package com.seventhdawn.gwt.rcx.server;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.ServletContext;

import com.google.gwt.user.server.rpc.RPC;
import com.google.gwt.user.server.rpc.SerializationPolicy;
import com.google.gwt.user.server.rpc.SerializationPolicyLoader;
import com.google.gwt.user.server.rpc.SerializationPolicyProvider;

/**
 * Abstract {@link com.google.gwt.user.server.rpc.SerializationPolicyProvider
 * SerializationPolicyProvider} implementation based on
 * {@link com.google.gwt.user.server.rpc.RemoteServiceServlet
 * RemoteServiceServlet}.
 * <p>
 * {@link javax.servlet.Filter Filters} and {@link javax.servlet.Servlet
 * servlets} can use this class for server call serialization purposes.
 * 
 * @author vojtech.szocs
 */
public abstract class RPCSerializationPolicyProvider implements SerializationPolicyProvider {

    private static final Logger logger = Logger.getLogger(RPCSerializationPolicyProvider.class.getName());

    protected abstract String getRequestContextPath();

    protected abstract ServletContext getServletContext();

    // following code is copied (and slightly modified) from
    // RemoteServiceServlet

    private final Map<String, SerializationPolicy> serializationPolicyCache = new HashMap<String, SerializationPolicy>();

    public final SerializationPolicy getSerializationPolicy(String moduleBaseURL, String strongName) {
        SerializationPolicy serializationPolicy = getCachedSerializationPolicy(moduleBaseURL, strongName);
        if (serializationPolicy != null) {
            return serializationPolicy;
        }

        serializationPolicy = loadSerializationPolicy(moduleBaseURL, strongName);

        if (serializationPolicy == null) {
            // Failed to get the requested serialization policy; use the default
            logger.log(Level.WARNING, "Failed to get the SerializationPolicy '" + strongName + "' for module '" + moduleBaseURL
                    + "'; a legacy, 1.3.3 compatible, serialization policy will be used.  You may experience SerializationExceptions as a result.");
            serializationPolicy = RPC.getDefaultSerializationPolicy();
        }

        // This could cache null or an actual instance. Either way we will not
        // attempt to lookup the policy again.
        putCachedSerializationPolicy(moduleBaseURL, strongName, serializationPolicy);

        return serializationPolicy;
    }

    private SerializationPolicy getCachedSerializationPolicy(String moduleBaseURL, String strongName) {
        synchronized (serializationPolicyCache) {
            return serializationPolicyCache.get(moduleBaseURL + strongName);
        }
    }

    private void putCachedSerializationPolicy(String moduleBaseURL, String strongName, SerializationPolicy serializationPolicy) {
        synchronized (serializationPolicyCache) {
            serializationPolicyCache.put(moduleBaseURL + strongName, serializationPolicy);
        }
    }

    protected SerializationPolicy loadSerializationPolicy(String moduleBaseURL, String strongName) {
        // The request can tell you the path of the web app relative to the
        // container root.
        String contextPath = getRequestContextPath();

        String modulePath = null;
        if (moduleBaseURL != null) {
            try {
                modulePath = new URL(moduleBaseURL).getPath();
            } catch (MalformedURLException ex) {
                // log the information, we will default
                logger.log(Level.WARNING, "Malformed moduleBaseURL: " + moduleBaseURL, ex);
            }
        }

        SerializationPolicy serializationPolicy = null;

        /*
         * Check that the module path must be in the same web app as the servlet
         * itself. If you need to implement a scheme different than this,
         * override this method.
         */
        if (modulePath == null || !modulePath.startsWith(contextPath)) {
            String message = "The module path requested, " + modulePath + ", is not in the same web application as this servlet, " + contextPath
                    + ".  Your module may not be properly configured or your client and server code maybe out of date.";
            logger.log(Level.SEVERE, message);
        } else {
            // Strip off the context path from the module base URL. It should be
            // a strict prefix.
            String contextRelativePath = modulePath.substring(contextPath.length());

            String serializationPolicyFilePath = SerializationPolicyLoader.getSerializationPolicyFileName(contextRelativePath + strongName);

            // Open the RPC resource file and read its contents.
            InputStream is = getServletContext().getResourceAsStream(serializationPolicyFilePath);
            try {
                if (is != null) {
                    try {
                        serializationPolicy = SerializationPolicyLoader.loadFromStream(is, null);
                    } catch (ParseException e) {
                        logger.log(Level.SEVERE, "Failed to parse the policy file '" + serializationPolicyFilePath + "'", e);
                    } catch (IOException e) {
                        logger.log(Level.SEVERE, "Could not read the policy file '" + serializationPolicyFilePath + "'", e);
                    }
                } else {
                    String message = "The serialization policy file '" + serializationPolicyFilePath
                            + "' was not found; did you forget to include it in this deployment?";
                    logger.log(Level.SEVERE, message);
                }
            } finally {
                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException e) {
                        // Ignore this error
                    }
                }
            }
        }

        return serializationPolicy;
    }

}
